import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';

class RloglineType {
  static final int normal = 0;
  static final int section = 1;
}

abstract class Rlogline {
  String text;
  int type;
  dynamic dat;
  bool _needDisplay = true;
  TextSpan? _span;
  String? _hilight;
  Rlogline(this.text, this.type);
  static TextStyle _hilightStyle = TextStyle(color: Colors.red);

  String get rawText => text;
  set rawText(String newText) {
    text = newText;
  }
  bool get needDisplay => _needDisplay;
  set needDisplay(bool visible) {
    _needDisplay = visible;
  }

  bool pending();
  RlogSection get section;

  void makeTextSpan(Iterable<FilterRuler> filterList, String hilight) {
    _hilight = hilight;
    _makeTextSpan(filterList);
    if (!text.contains(hilight)) return;
    TextSpan span = _span as TextSpan;
    if (span.text != null) {
      _span = hilightText(text, hilight, _hilightStyle);
    } else if (span.children!.isNotEmpty) {
      for (int i = 0; i < span.children!.length; i++) {
        final child = span.children![i] as TextSpan;
        if (child.text?.contains(hilight) ?? false) {
          _span!.children![i] =
              hilightText(child.text!, hilight, _hilightStyle);
        }
      }
    }
  }

  void _makeTextSpan(Iterable<FilterRuler> filterList) {
    if (filterList.isEmpty) {
      _span = TextSpan(text: text);
      return;
    }
    var target = <Object>[text];
    filterList.forEach((filter) {
      filter.colorize(target);
    });
    _span = TextSpan(
        children: target.map((e) {
      if (e.runtimeType == String) {
        return TextSpan(text: e as String);
      }
      return e as TextSpan;
    }).toList());
  }

  TextSpan get textSpan {
    return _span ?? TextSpan(text: text);
  }

  void clearTextSpan() {
    _span = TextSpan(text: text);
  }
}

TextSpan hilightText(String text, String hilight, TextStyle textStyle) {
  int start = 0;
  List<TextSpan> list = [];
  text.replaceAllMapped(hilight, (match) {
    if (match.start > start) {
      final substr = text.substring(start, match.start);
      list.add(TextSpan(text: substr));
    }
    list.add(TextSpan(text: match.group(0), style: textStyle));
    start = match.end;
    return "";
  });
  if (start != 0 && start < text.length) {
    list.add(TextSpan(text: text.substring(start)));
  }
  if (list.isEmpty) return TextSpan(text: text);
  if (list.length == 1) return list[0];
  return TextSpan(children: list);
}

class RlogSection extends Rlogline {
  bool finish = false;
  final _children = <RlogText>[];
  List<RlogText>? _cache;
  int _cachePos = 0;
  var _dispChildrenCount = 0;
  RlogSection(String text) : super(text, RloglineType.section);

  @override
  bool pending() {
    return !this.finish;
  }

  @override
  RlogSection get section {
    return this;
  }

  int get childrenCount => _children.length;
  int get dispChildrenCount => _dispChildrenCount;
  Iterable<Rlogline> get children => _children;
  Iterable<RlogText> get displayChilren =>
      _children.where((element) => element.needDisplay);

  void addLines(Iterable<RlogText> lines) {
    // assert line.display = true;
    _children.addAll(lines);
    _dispChildrenCount += lines.length;
  }

  void addLine(RlogText line) {
    _children.add(line);
    _dispChildrenCount ++;
  }

  void resetChildrenDisplayable(bool visible) {
    _children.forEach((line) {
      line._needDisplay = visible;
    });
    _dispChildrenCount = visible ? _children.length : 0;
    _cache = null;
  }

  void setChildrenDisplayable(bool Function(Rlogline line) testDisplay) {
    int count = 0;
    _children.forEach((line) {
      final visible = testDisplay(line);
      if (visible) count++;
      line._needDisplay = visible;
    });
    _dispChildrenCount = count;
    _cache = null;
  }

  void onChildrenDisplayableChange(Rlogline line, bool visible) {
    if (visible)
      _dispChildrenCount++;
    else
      _dispChildrenCount--;
    _cache = null;
  }

  void prepareChildren(int pos, {int maxNum = 200}) {
    if (_cache != null) {
      final off = pos - _cachePos;
      if (pos < -maxNum ||
          (pos >= 0 && off < 0) ||
          (off >= _cache!.length && pos < dispChildrenCount)) {
        _cache = null;
      }
    }
    if (_cache == null && pos >= 0 && pos < dispChildrenCount) {
      final cachePos = (pos / maxNum).floor() * maxNum;
      _cache = List<RlogText>.of(displayChilren.skip(cachePos).take(maxNum));
      _cachePos = cachePos;
    }
  }

  Rlogline? displayChildAt(int pos) {
    return _cache?[pos - _cachePos];
  }

  Rlogline childAt(int pos) {
    return _children[pos];
  }
}

class RlogText extends Rlogline {
  late RlogSection parent;
  RlogText(String text, RlogSection section)
      : super(text, RloglineType.normal) {
    this.parent = section;
  }

  @override
  bool pending() {
    return parent.pending();
  }

  @override
  RlogSection get section {
    return parent;
  }

  @override
  set needDisplay(bool visible) {
    if (needDisplay == visible) return;
    super.needDisplay = visible;
    parent.onChildrenDisplayableChange(this, visible);
  }
}

class FilterRuler {
  static final colorList = [
    Colors.red[200],
    Colors.yellow[200],
    Colors.green[200],
    Colors.orange[200],
    Colors.brown[200],
    Colors.pink[300],
    Colors.purple[200],
  ];
  static int index = 0;
  static Color selectColor() {
    return colorList[(++index) % colorList.length] ??
        Color.fromRGBO(150, 150, 250, 0.8);
  }

  bool _isExclude = false;
  Pattern? _pattern;
  String? _text;
  TextStyle _textStyle;
  bool enable = true;
  FilterRuler(String text, {bool isExclude = false, bool reg = false})
      : _textStyle = TextStyle(backgroundColor: selectColor()) {
    if (reg)
      _pattern = RegExp(text);
    else
      _text = text;
    _isExclude = isExclude;
  }

  TextStyle get textSTyle => _textStyle;
  bool get isExclude => _isExclude;
  bool get isReg => _pattern != null;

  @override
  bool operator ==(Object other) {
    if (other.runtimeType != FilterRuler) return false;
    FilterRuler c = other as FilterRuler;
    return c._text == this._text &&
        c._pattern == this._pattern &&
        c._isExclude == this._isExclude;
  }

  bool testDisplay(Rlogline line) {
    // 在可显示的行中进行排除, 在不可显示的行中进行添加
    if (_isExclude == !line.needDisplay) return line.needDisplay;
    final ret = line.text.contains(_text ?? _pattern!);
    if (ret) {
      line.needDisplay = !_isExclude;
    }
    return line.needDisplay;
  }

  String toString() {
    return _text ?? _pattern.toString();
  }

  @override
  int get hashCode => (_text ?? _pattern).hashCode + (_isExclude ? 0 : 1);

  void colorize(List<Object> list) {
    for (int i = 0; i < list.length; i++) {
      final item = list[i];
      if (item.runtimeType != String) continue;
      var text = item as String;
      int start = 0;
      text.replaceAllMapped(_text ?? _pattern!, (match) {
        if (match.start > start) {
          list[i] = text.substring(start, match.start);
          list.insert(++i, TextSpan(text: match.group(0), style: textSTyle));
        } else {
          list[i] = TextSpan(text: match.group(0), style: textSTyle);
        }
        start = match.end;
        return "";
      });
      if (start != 0 && start < text.length) {
        list.insert(++i, text.substring(start));
      }
    }
  }
}
